﻿using System;
using System.Collections.Generic;
using Btstack;

namespace test
{
    class Program
    {
        public static Dictionary<string, bool> Devices = new Dictionary<string, bool>();
        public static ushort ConnectionHandle;
        public static ushort ServiceUuid = 0x1000;
        public static ushort CharacteristicRxUuid = 0x1001;
        public static ushort CharacteristicTxUuid = 0x1002;
        public static Gatt.ClientServiceType service;
        public static Gatt.ClientCharacteristicType CharacteristicRx;
        public static Gatt.ClientCharacteristicType CharacteristicTx;
        public static Gatt.ClientNotificationType NotificationListener;
        public static bool ListenerRegistered = false;

        public static void RegisterHandlerForNotifications()
        {
            ListenerRegistered = true;

            Gatt.ClientListenForCharacteristicValueUpdates(out NotificationListener, (byte type, UInt16 channel, byte[] packet) =>
            {
                switch (Hci.EventPacketGetType(packet))
                {
                    case Gatt.GATT_EVENT_NOTIFICATION:
                        var data = Gatt.EventNotificationGetValue(packet);
                        Console.WriteLine(BitConverter.ToString(data));
                        break;
                    case Gatt.GATT_EVENT_QUERY_COMPLETE:
                        break;
                    case Gatt.GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE:
                        break;
                    default:
                        Console.WriteLine("Unknown packet type 0x{0:x2}\n", Hci.EventPacketGetType(packet));
                        break;
                }
            }, ConnectionHandle, CharacteristicTx);

            Console.WriteLine("Enable notify on test characteristic");
            Gatt.ClientWriteClientCharacteristicConfiguration((byte type, UInt16 channel, byte[] packet) =>
            {
                switch (Hci.EventPacketGetType(packet))
                {
                    case Gatt.GATT_EVENT_QUERY_COMPLETE:
                        var status = Gatt.EventQueryCompleteGetAttStatus(packet);
                        Console.WriteLine($"Notifications enabled, ATT status {status:x2}");
                        break;
                    default:
                        break;
                }
            }, ConnectionHandle, CharacteristicTx, Gatt.GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
        }

        public static void ClientDiscoverCharacteristicsTx()
        {
            Console.WriteLine($"Search for TX characteristic: 0x{CharacteristicTxUuid:x2}");

            Gatt.ClientDiscoverCharacteristicsForServiceByUuid16((byte type, UInt16 channel, byte[] packet) =>
            {
                switch (Hci.EventPacketGetType(packet))
                {
                    case Gatt.GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
                        Gatt.EventCharacteristicQueryResultGetCharacteristic(packet, out CharacteristicTx);
                        break;
                    case Gatt.GATT_EVENT_QUERY_COMPLETE:
                        if (packet[4] != 0)
                        {
                            Console.WriteLine($"CHARACTERISTIC_QUERY_RESULT - Error status {packet[4]:x}.");
                            Gap.Disconnect(ConnectionHandle);
                            break;
                        }

                        RegisterHandlerForNotifications();
                        break;
                    default:
                        break;
                }
            }, ConnectionHandle, service, CharacteristicTxUuid);
        }

        public static void ClientDiscoverCharacteristicsRx()
        {
            Console.WriteLine($"Search for RX characteristic: 0x{CharacteristicRxUuid:x2}");

            Gatt.ClientDiscoverCharacteristicsForServiceByUuid16((byte type, UInt16 channel, byte[] packet) =>
            {
                switch (Hci.EventPacketGetType(packet))
                {
                    case Gatt.GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
                        Gatt.EventCharacteristicQueryResultGetCharacteristic(packet, out CharacteristicRx);
                        break;
                    case Gatt.GATT_EVENT_QUERY_COMPLETE:
                        if (packet[4] != 0)
                        {
                            Console.WriteLine($"CHARACTERISTIC_QUERY_RESULT - Error status {packet[4]:x}.");
                            Gap.Disconnect(ConnectionHandle);
                            break;
                        }

                        Gatt.ClientWriteValueOfCharacteristicWithoutResponse(ConnectionHandle, CharacteristicRx.ValueHandle, new byte[] { 0xab, 0xcd, 0xef });

                        ClientDiscoverCharacteristicsTx();
                        break;
                    default:
                        break;
                }
            }, ConnectionHandle, service, CharacteristicRxUuid);
        }

        public static void ClientDiscoverPrimaryServices()
        {
            Console.WriteLine($"Search for service: 0x{ServiceUuid:x2}");

            Gatt.ClientDiscoverPrimaryServicesByUuid16((byte type, UInt16 channel, byte[] packet) =>
            {
                switch (Hci.EventPacketGetType(packet))
                {
                    case Gatt.GATT_EVENT_SERVICE_QUERY_RESULT:
                        Gatt.EventServiceQueryResultGetService(packet, out service);
                        break;
                    case Gatt.GATT_EVENT_QUERY_COMPLETE:
                        if (packet[4] != 0)
                        {
                            Console.WriteLine($"SERVICE_QUERY_RESULT - Error status {packet[4]:x}");
                            Gap.Disconnect(ConnectionHandle);
                            break;
                        }

                        ClientDiscoverCharacteristicsRx();
                        break;
                    default:
                        break;
                }
            }, ConnectionHandle, 0x1000);
        }

        public static void BtstackMain()
        {
            L2cap.Init();

            Sm.Init();
            Sm.SetIoCapabilities(Sm.IoCapability.NoInputNoOutput);

            Gatt.ClientInit();

            Hci.AddEventHandler((type, channel, packet) =>
            {
                if (type != Hci.HCI_EVENT_PACKET)
                    return;

                switch (Hci.EventPacketGetType(packet))
                {
                    case Btstack.Btstack.BTSTACK_EVENT_STATE:
                        if ((Hci.HciState)Btstack.Btstack.EventStateGetState(packet) != Hci.HciState.Working)
                            return;
                        Console.WriteLine("Start scanning!");
                        Gap.SetScanParameters(0, 0x0030, 0x0030);
                        Gap.StartScan();
                        break;

                    case Gap.GAP_EVENT_ADVERTISING_REPORT:
                        var AddrType = Gap.EeventAdvertisingReportGetAddressType(packet);
                        var addr = Gap.EventAdvertisingReportGetAddressStr(packet);
                        var name = Gap.AdvertisementReportName(packet);

                        if (Devices.TryGetValue(addr, out bool _))
                            return;

                        Devices[addr] = true;

                        Console.WriteLine($"Addr-Type: {AddrType}, Addr: {addr}, Name: {name}");

                        if (name == "zjh")
                        {
                            Gap.StopScan();
                            Console.WriteLine($"Stop scan. Connect to device with addr {addr}");
                            Gap.Connect(addr, AddrType);
                        }
                        break;

                    case Hci.HCI_EVENT_LE_META:
                        // wait for connection complete
                        if (Hci.EventLeMetaGetSubeventCode(packet) != Hci.HCI_SUBEVENT_LE_CONNECTION_COMPLETE)
                            break;

                        ConnectionHandle = Hci.SubeventLeConnectionCompleteGetConnectionHandle(packet);

                        // print connection parameters (without using float operations)
                        var conn_interval = Hci.SubeventLeConnectionCompleteGetConnInterval(packet);
                        Console.WriteLine("Connection Interval: {0} ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3));
                        Console.WriteLine("Connection Latency: {0}\n", Hci.SubeventLeConnectionCompleteGetConnLatency(packet));
                        // initialize gatt client context with handle, and add it to the list of active clients
                        // query primary services
                        ClientDiscoverPrimaryServices();
                        break;

                    case Hci.HCI_EVENT_DISCONNECTION_COMPLETE:
                        ConnectionHandle = Hci.HCI_CON_HANDLE_INVALID;

                        if (ListenerRegistered)
                        {
                            Gatt.ClientStopListeningForCharacteristicValueUpdates(NotificationListener);
                        }

                        Console.WriteLine("Disconnected");
                        break;

                    default:
                        break;
                }
            });

            Hci.PowerControl(Hci.HciPowerMode.On);
        }

        static void Main(string[] args)
        {
            Console.WriteLine("BTstack/windows-winusb booting up");

            Hci.DumpEnableLogLevel(Hci.HCI_DUMP_LOG_LEVEL_DEBUG, false);
            Hci.DumpEnableLogLevel(Hci.HCI_DUMP_LOG_LEVEL_INFO, false);
            Hci.DumpEnableLogLevel(Hci.HCI_DUMP_LOG_LEVEL_ERROR, false);

            Btstack.Btstack.MemoryInit();

            Btstack.Btstack.RunLoopInit(Btstack.Btstack.RunLoopWindowsGetInstance());

            Console.CancelKeyPress += delegate {
                Console.WriteLine("CTRL-C - SIGINT received, shutting down..");

                // power down
                Hci.PowerControl(Hci.HciPowerMode.Off);
                Hci.Close();
            };

            var transport = Hci.TransportUsbInstance();

            Hci.Init(transport, IntPtr.Zero);

            Hci.AddEventHandler((type, channel, packet) =>
            {
                if (type != Hci.HCI_EVENT_PACKET)
                    return;

                if (Hci.EventPacketGetType(packet) != Btstack.Btstack.BTSTACK_EVENT_STATE)
                    return;

                if ((Hci.HciState)Btstack.Btstack.EventStateGetState(packet) != Hci.HciState.Working)
                    return;

                Console.WriteLine("BTstack up and running at {0}\n", Gap.LocalBdAddrStr());
            });

            BtstackMain();

            Btstack.Btstack.RunLoopExecute();
        }
    }
}
